local M = {}

local generic_opts_any = { noremap = true, silent = true }

local generic_opts = {
    i = generic_opts_any,   -- insert
    n = generic_opts_any,   -- normal
    v = generic_opts_any,   -- visual
    x = generic_opts_any,   -- visual_block
    c = generic_opts_any,   -- command
    o = generic_opts_any,   -- operator_pending
    t = { silent = true },  -- term
}

local defaults = {
    i = {
        ["<C-s>"] = "<ESC>:w<CR>",
        ["<C-q>"] = "<ESC>:q<CR>",
        --[[ move current line/block ]]
        ["<C-k>"] = "<Esc>:m .+1<CR>==gi",
        ["<C-i>"] = "<Esc>:m .-2<CR>==gi",
    },

    --[[
        file head/tail
        head: gg/ [ [
        tail: G/ ] ]

        macro record
        qf -- start record
        operations
        q -- end record
        exec "<number>@f" in normal mode.
    --]]
    n = {
        ["="] = "nzz", -- next search target
        ["-"] = "Nzz", -- prev search target
        ["<leader><CR>"] = ":nohlsearch<CR>", -- cancel search target highlight
        [";"] = ":",
        ["<C-s>"] = ":w<CR>",
        ["<C-q>"] = ":q<CR>",
        ["Q"] = ":q!<CR>",
        ["s"] = "<Nop>",
        --[[ replace string ]]
        --["<C-h>"] = ":s//g<left><left>", -- replace current line
        ["<C-h>"] = ":%s//g<left><left>", -- replace all match
        --[[ direction keys ]]
        ["i"] = "k",
        ["I"] = "5k",
        ["k"] = "j",
        ["K"] = "5j",
        ["j"] = "h",
        ["J"] = "b", -- prev word
        ["L"] = "w", -- next word
        --[[ window operations ]]
        ["sl"] = ":set splitright<CR>:vsplit<CR>",      -- split window horizontal
        ["sj"] = ":set nosplitright<CR>:vsplit<CR>",    -- split window horizontal
        ["si"] = ":set nosplitbelow<CR>:split<CR>",     -- split window vertical
        ["sk"] = ":set splitbelow<CR>:split<CR>",       -- split window vertical
        -- select window
        ["<leader>i"] = "<C-w>k",
        ["<leader>j"] = "<C-w>h",
        ["<leader>k"] = "<C-w>j",
        ["<leader>l"] = "<C-w>l",
        -- resize window
        ["<up>"] = ":res +2<CR>",
        ["<down>"] = ":res -2<CR>",
        ["<left>"] = ":vertical resize-2<CR>",
        ["<right>"] = ":vertical resize+2<CR>",
        -- setlayout
        ["sv"] = "<C-w>t<C-w>H",
        ["sh"] = "<C-w>t<C-w>K",
        --[[ tab operations ]]
        ["tn"] = ":tabe<CR>",       -- new tab
        ["tj"] = ":-tabnext<CR>",   -- select tab
        ["tl"] = ":+tabnext<CR>",   -- select tab
        ["tu"] = ":tabc<CR>",       -- close tab; also can use <C-q>
        --
        ["h"] = "i",
        ["H"] = "I",
        ["<leader>v"] = "<C-q>", -- visual block mode
        --[[ move current line/block ]]
        ["<C-k>"] = ":m .+1<CR>==",
        ["<C-i>"] = ":m .-2<CR>==",
        --[[ move to head or tail ]]
        ["9"] = "0",  -- head
        ["0"] = "$",  -- tail
    },

    v = {
        ["i"] = "k",
        ["k"] = "j",
        ["j"] = "h",
        --[[ indent ]]
        ["<"] = "<gv",
        [">"] = ">gv",
        --[[ move to head or tail ]]
        ["9"] = "0",  -- head
        ["0"] = "$",  -- tail
    },

    x = {
        --[[ move current line/block ]]
        ["<C-k>"] = ":m '>+1<CR>gv-gv",
        ["<C-i>"] = ":m '<-2<CR>gv-gv",
        --[[ move to head or tail ]]
        ["9"] = "0",  -- head
        ["0"] = "$",  -- tail
    },

    c = {
        ["<C-i>"] = { 'pumvisible() ? "\\<C-p>" : "\\<C-p>"', { expr = true, noremap = true } },
        ["<C-k>"] = { 'pumvisible() ? "\\<C-n>" : "\\<C-n>"', { expr = true, noremap = true } },
    }
}


M.load = function(configuration)
    for mode, mapping in pairs(configuration) do
        local opt = generic_opts[mode] or generic_opts_any
        for key, val in pairs(mapping) do
            if type(val) == "table" then
                opt = val[2]
                val = val[1]
            end
            vim.keymap.set(mode, key, val, opt)
        end
    end
end

M.init = function()
    vim.api.nvim_set_keymap("", "<Space>", "<Nop>", { noremap = true })
    vim.g.mapleader = " "
    vim.g.maplocalleader = " "

    M.load(defaults)
end

return M